package com.fulihui.usercore.common.util;

import org.apache.commons.codec.binary.Base64;
import org.near.toolkit.security.codec.AESCoder;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.Optional;

import static org.near.toolkit.security.codec.AESCoder.encryptBytes;

/**
 * @author Willard.Hu on 2016/11/21 0021.
 */
public class DataSafe {

    private static final int    KEY_LENGTH_BIT_SIZE = 2;
    private static final String AES_PREFIX          = "AES-";
    private static final String BASE_PREFIX         = "BASE-";

    /**
     * 判断是否是 BASE64 编码
     *
     * @param base64 base64 编码值
     * @return true / false
     */
    public static boolean isBaseEncode(String base64) {
        return base64 != null && base64.indexOf(BASE_PREFIX) == 0;
    }

    /**
     * 获取进行 BASE64 编码后的值
     *
     * @param src 原文
     * @return Optional 包装后的编码值
     */
    public static Optional<String> baseEncode(String src) {
        if (src == null) {
            return Optional.empty();
        }
        return Optional.of(BASE_PREFIX + Base64.encodeBase64String(src.getBytes()));
    }

    /**
     * 获取 BASE64 解码后的值
     *
     * @param base64 编码后的值
     * @return Optional 包装后的解码值
     */
    public static Optional<String> baseDecode(String base64) {
        if (base64 == null) {
            return Optional.empty();
        }
        return Optional.of(new String(Base64.decodeBase64(base64.substring(BASE_PREFIX.length()))));
    }

    /**
     * 判断是否是 AES 加密
     *
     * @param encrypted 加密后数据
     * @return true / false
     */
    public static boolean isAesEncrypt(String encrypted) {
        return encrypted != null && encrypted.indexOf(AES_PREFIX) == 0;
    }

    /**
     * Aes 数据加密，Key会混在密文中
     */
    public static String aesEncrypt(String data, String key) {
        Assert.hasLength(data);
        Assert.hasLength(key);
        byte[] bytes = encryptBytes(data, key);
        byte[] keyBuff = Base64.decodeBase64(key);
        byte[] newBuff = Arrays.copyOf(toLengthBit(keyBuff.length),
                KEY_LENGTH_BIT_SIZE + bytes.length + keyBuff.length);
        // m标识bytes的索引增长量，n标识keyBuff的增长量
        int m = 0, n = 0;

        for (int i = 2; i < newBuff.length; i++) {
            if (i % 2 == 0) {
                if (m < bytes.length) {
                    newBuff[i] = bytes[m];
                    m++;
                } else {
                    newBuff[i] = keyBuff[n];
                    n++;
                }
            } else {
                if (n < keyBuff.length) {
                    newBuff[i] = keyBuff[n];
                    n++;
                } else {
                    newBuff[i] = bytes[m];
                    m++;
                }
            }
        }
        return AES_PREFIX + Base64.encodeBase64String(newBuff);
    }

    /**
     * Aes 数据解密
     */
    public static String aesDecrypt(String encrypted) {
        Assert.hasLength(encrypted);
        encrypted = encrypted.substring(AES_PREFIX.length());
        byte[] bytes = Base64.decodeBase64(encrypted);
        int keylen = toLengthNumber(bytes);
        int datalen = bytes.length - KEY_LENGTH_BIT_SIZE - keylen;
        byte[] data = new byte[datalen];
        byte[] keybyte = new byte[keylen];

        // m标识bytes的索引增长量，n标识keyBuff的增长量
        int m = 0, n = 0;
        for (int i = 2; i < bytes.length; i++) {
            if (i % 2 == 0) {
                if (m < datalen) {
                    data[m] = bytes[i];
                    m++;
                } else {
                    keybyte[n] = bytes[i];
                    n++;
                }
            } else {
                if (n < keylen) {
                    keybyte[n] = bytes[i];
                    n++;
                } else {
                    data[m] = bytes[i];
                    m++;
                }
            }
        }
        return AESCoder.decrypt(data, keybyte);
    }

    /**
     * 判断加密类型自动解密，如果未加密，直接返回原文
     *
     * @param encrypted 密文
     * @return 解密后字符串
     */
    public static String autoDecrypt(String encrypted) {
        String src;
        if (isBaseEncode(encrypted)) {
            Optional<String> optional = baseDecode(encrypted);
            src = optional.isPresent() ? optional.get() : null;
        } else if (isAesEncrypt(encrypted)) {
            src = aesDecrypt(encrypted);
        } else {
            src = encrypted;
        }
        return src;
    }

    private static byte[] toLengthBit(int length) {
        byte[] bit = new byte[KEY_LENGTH_BIT_SIZE];
        bit[1] = (byte) (length & 0xFF);
        bit[0] = (byte) (length >> 8 & 0xFF);
        return bit;
    }

    private static int toLengthNumber(byte[] bit) {
        int sourceNumber = 0;
        for (int i = 0; i < KEY_LENGTH_BIT_SIZE; i++) {
            sourceNumber <<= 8;
            sourceNumber |= bit[i] & 0xFF;
        }
        return sourceNumber;
    }

}
